/*
 * BaseNetFeatureIO.java
 * 
 * Created by demory on Jul 6, 2009, 10:29:43 AM
 * 
 * Copyright 2008 David D. Emory
 * 
 * This file is part of Five Points. See <http://www.fpdev.org> for
 * additional information regarding the project.
 * 
 * Five Points is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Five Points is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Five Points.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.fpdev.core.basenet;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.Point;
import java.awt.geom.Point2D;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.fpdev.core.FPEngine;
import org.fpdev.core.Scenario;
import org.geotools.data.DataUtilities;
import org.geotools.data.DefaultTransaction;
import org.geotools.data.FeatureStore;
import org.geotools.data.Transaction;
import org.geotools.feature.DefaultFeatureCollection;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.SchemaException;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;

/**
 *
 * @author demory
 */
public class BaseNetFeatureIO {

  private FPEngine engine_;

  private GeometryFactory gf_;

  private SimpleFeatureType nodeFT_, linkFT_;

  public BaseNetFeatureIO(FPEngine engine) {
    engine_ = engine;
    gf_ = new GeometryFactory();

    try {
      nodeFT_ = DataUtilities.createType("nodes", "geom:Point,fpid:Integer,scen:Integer");
      linkFT_ = DataUtilities.createType("links", "geom:MultiLineString,fpid:Integer,scen:Integer,exttype:Integer,name:String,fnode:Integer,tnode:Integer,len:Float,upfw:Float,upbw:Float,elevarr:String,torarr:String,faddrl:Integer,taddrl:Integer,faddrr:Integer,taddrr:Integer,zipl:Integer,zipr:Integer");
    } catch (SchemaException ex) {
      Logger.getLogger(BaseNetFeatureIO.class.getName()).log(Level.SEVERE, null, ex);
    }

  }

  public SimpleFeatureType getNodeFeatureType() {
    return nodeFT_;
  }

  public SimpleFeatureType getNodeFeatureTypePostgis() { // HACK -- until geotools Point/PointM bug is fixed
    try {
      return nodeFT_ = DataUtilities.createType("nodes", "geom:Geometry,fpid:Integer,scen:Integer");
    } catch (SchemaException ex) {
      Logger.getLogger(BaseNetFeatureIO.class.getName()).log(Level.SEVERE, null, ex);
    }
    return null;
  }

  public SimpleFeatureType getLinkFeatureType() {
    return linkFT_;
  }


  public BNode readNode(SimpleFeature f) {
    int nodeID = ((Integer) f.getAttribute("fpid")).intValue();
    Scenario scen = engine_.getDataPackage().getScenarios().get(((Integer) f.getAttribute("scen")).intValue());


    Geometry g = (Geometry) f.getDefaultGeometry();
    Coordinate[] coords = g.getCoordinates();

    return new BNode(nodeID, scen, coords[0].x, coords[0].y);
  }

  public BLink readLink(SimpleFeature f) {
    int id = ((Integer) f.getAttribute("fpid")).intValue();
    int scenID = ((Integer) f.getAttribute("scen")).intValue();
    Scenario s = engine_.getDataPackage().getScenarios().get(scenID);
    int type = ((Integer) f.getAttribute("exttype")).intValue();
    int fNodeID = ((Integer) f.getAttribute("fnode")).intValue();
    int tNodeID = ((Integer) f.getAttribute("tnode")).intValue();
    String name = f.getAttribute("name").toString();
    int len = (int) (new Double(f.getAttribute("len").toString())).doubleValue();

    BNode fNode = engine_.getBaseNet().getNodeFromID(fNodeID);
    if(fNode == null) {
      return null;
      //System.out.println("node from id");
      //fNode = engine_.getDataPackage().getDataConn().nodeFromID(fNodeID);
    }

    BNode tNode = engine_.getBaseNet().getNodeFromID(tNodeID);
    if(tNode == null) {
      return null;
      //System.out.println("node from id");
      //tNode = engine_.getDataPackage().getDataConn().nodeFromID(tNodeID);
    }

    if (fNode == null || tNode == null) {
      System.out.println("could not load link w/ id=" + id);
      return null;
    }
    double fx = fNode.getX(), fy = fNode.getY();

    //CNode tNode = (CNode)	nodes_.find(tNodeID).element();
    double tx = tNode.getX(), ty = tNode.getY();

    BLink link = null;
    int linkType = (type > 100) ? type / 100 : type;
    //if(id % 100 ==0) System.out.println(""+linkType);
    switch (linkType / 10) {
      case LinkTypes.MAJOR_TFARE:
      case LinkTypes.MINOR_TFARE:
      case LinkTypes.OTHER_STREET:
      case LinkTypes.NONWALKABLE:
        link = new BLinkStreet(s, id, linkType, fNode, tNode, name, len);
        /*int faddrr = ((Integer) f.getAttribute("faddrr")).intValue();
        int taddrr = ((Integer) f.getAttribute("taddrr")).intValue();
        int faddrl = ((Integer) f.getAttribute("faddrl")).intValue();
        int taddrl = ((Integer) f.getAttribute("taddrl")).intValue();
        int zipr = ((Integer) f.getAttribute("zipr")).intValue();
        int zipl = ((Integer) f.getAttribute("zipl")).intValue();
        ((BLinkStreet) link).setAddressRange(faddrl, faddrr, taddrl, taddrr);
        ((BLinkStreet) link).setZips(zipl, zipr);*/
        if (type > 100) {
          if (type % 10 > 0) ((BLinkStreet) link).setBikePedProv(type % 10);
          if (type/10 % 10 > 0) ((BLinkStreet) link).setTransitProv(type/10 % 10);
        }
        break;

      case LinkTypes.BIKE_PED:
        link = new BLinkBikePed(s, id, fNode, tNode, name, len);
        break;
      case LinkTypes.TRANSIT:
        if (linkType == LinkTypes.TR_METRO) {
          link = new BLinkMetro(s, id, fNode, tNode, name, len);
        }
        if (linkType == LinkTypes.TR_LRT) {
          link = new BLinkLRT(s, id, fNode, tNode, name, len);
        }
        break;
      case LinkTypes.RAIL:
        link = new BLinkRR(s, id, fNode, tNode, name, len);
        break;
    }
    if (link != null) {

      //engine_.getBaseNet().addLink(link);

      link.setUpSlopeBW(new Float(f.getAttribute("upbw").toString()).floatValue());
      link.setUpSlopeFW(new Float(f.getAttribute("upfw").toString()).floatValue());

      String elevArr = f.getAttribute("elevarr").toString();
      //System.out.println("elevarr="+elevArr);
      if(elevArr.trim().length() > 0) {
        String elevs[] = elevArr.split(",");
        List<Integer> elevList = new LinkedList<Integer>();
        for(String elev : elevs) {
          //System.out.println("elev="+elev);
          elevList.add(new Integer(elev));
        }
        link.setElevList(elevList);
      }

      String overrideArr = f.getAttribute("torarr").toString();
      if(overrideArr.trim().length() > 0) {
        String overrides[] = overrideArr.split(",");
        for(String tor : overrides) {
          String[] torSplit = tor.split(":");
          engine_.getBaseNet().setScenLinkOverride(link, new Short(torSplit[0]), new Integer(torSplit[1]));

        }
      }

      Geometry g = (Geometry) f.getDefaultGeometry();
      Coordinate[] coords = g.getCoordinates();

      List<Point2D.Double> shpPts = new LinkedList<Point2D.Double>();
      for(int i=1; i < coords.length-1; i++) {
        shpPts.add(new Point2D.Double(coords[i].x, coords[i].y));
      }
      if(!shpPts.isEmpty()) link.setShapePoints(shpPts);
    }

    return link;
  }

  public void writeNode(BNode node, FeatureStore store) {
    writeNodes(Collections.singleton(node), store);
  }
   
  public void writeNodes(Collection<BNode> nodes, FeatureStore store) {//FeatureWriter writer) {
    try {
      /*SimpleFeature feature = (SimpleFeature) writer.next();
      Point point = gf_.createPoint(new Coordinate(node.getX(), node.getY()));
      feature.setDefaultGeometry(point);
      feature.setAttribute("fpid", node.getID());
      feature.setAttribute("scen", node.getScenario().getID());
      writer.write();*/

      FeatureCollection coll = new DefaultFeatureCollection("nodes", nodeFT_);

      for(BNode node : nodes) {
        Point point = constructNodeGeometry(node); //gf_.createPoint(new Coordinate(node.getX(), node.getY()));
        int fpid = node.getID();
        int scen = node.getScenario().getID();
        //nodeFT_.


        String fid = "nodes."+Integer.toString(fpid);
        SimpleFeature feature = SimpleFeatureBuilder.build(nodeFT_, new Object[]{point, fpid, scen}, fid );
        //System.out.println("fid="+feature.getID()+" fpid="+fpid);
        coll.add(feature);
      }

      Transaction transaction = new DefaultTransaction("handle");
      store.setTransaction(transaction);
      store.addFeatures(coll);
      transaction.commit();
      transaction.close();

    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }

  public Point constructNodeGeometry(BNode node) {
    return gf_.createPoint(new Coordinate(node.getX(), node.getY()));
  }

  public void writeLink(BLink link, FeatureStore store) {
    writeLinks(Collections.singleton(link), store);
  }

  public void writeLinks(Collection<BLink> links, FeatureStore store) { //BLink link, FeatureWriter writer) {
    try {

      FeatureCollection coll = new DefaultFeatureCollection("links", linkFT_);
      SimpleFeatureBuilder builder = new SimpleFeatureBuilder(linkFT_);

      for(BLink link : links) {
        
        if(link.getID() <= 0) continue;



        //SimpleFeature feature = (SimpleFeature) writer.next();
        SimpleFeature feature = builder.buildFeature(Integer.toString(link.getID()));

        feature.setDefaultGeometry(constructLinkGeometry(link));
        feature.setAttribute("fpid", link.getID());
        feature.setAttribute("scen", link.getScenario().getID());
        feature.setAttribute("exttype", link.getExtendedType());
        feature.setAttribute("name", link.getDisplayName());
        feature.setAttribute("fnode", link.getFNodeID());
        feature.setAttribute("tnode", link.getTNodeID());
        feature.setAttribute("len", link.getMeasuredLength());
        if(link.getClassType() == BLink.CLASS_STREET) {
          BLinkStreet street = (BLinkStreet) link;
          feature.setAttribute("faddrr", street.getFAddrR());
          feature.setAttribute("faddrl", street.getFAddrL());
          feature.setAttribute("taddrr", street.getTAddrR());
          feature.setAttribute("taddrl", street.getTAddrL());
          feature.setAttribute("zipr", street.getZipR());
          feature.setAttribute("zipl", street.getZipL());
        }
        
        feature.setAttribute("elevarr", getElevationArray(link));
        feature.setAttribute("upfw", link.getUpSlopeFW());
        feature.setAttribute("upbw", link.getUpSlopeBW());

        feature.setAttribute("torarr", getTypeOverrideArray(link));

        coll.add(feature);
      }

      Transaction transaction = new DefaultTransaction("handle");
      store.setTransaction(transaction);
      store.addFeatures(coll);
      transaction.commit();
      transaction.close();

      //writer.write();
    } catch (IOException ex) {
      ex.printStackTrace();
    }

  }

  public MultiLineString constructLinkGeometry(BLink link) {
    Coordinate start = new Coordinate(link.getX1(), link.getY1());
    Coordinate end = new Coordinate(link.getX2(), link.getY2());


    List<Point2D.Double> shpPts = link.getShapePointsList();
    Coordinate[] coords = new Coordinate[2 + shpPts.size()];

    coords[0] = start;
    Iterator<Point2D.Double> spIter = shpPts.iterator();
    int spi = 1;
    while (spIter.hasNext()) {
      Point2D.Double pt = spIter.next();
      coords[spi] = new Coordinate(pt.x, pt.y);
      spi++;
    }
    coords[1 + shpPts.size()] = end;

    LineString line = gf_.createLineString(coords);
    LineString[] lines = new LineString[1];
    lines[0] = line;

    return gf_.createMultiLineString(lines);
  }

  public String getElevationArray(BLink link) {
    String elevStr = "";
    int[] elevArr = link.getElevArray();
    if(elevArr != null) {
      for(int i = 0; i < elevArr.length; i++)
        elevStr += elevArr[i] + (i == elevArr.length-1 ? "" : ",");
    }
    return elevStr;
  }

  public String getTypeOverrideArray(BLink link) {
    String overrideStr = "";
    Map<Short, Integer> overrides = engine_.getBaseNet().getScenLinkOverrides(link.getID());
    if(overrides != null) {
      Iterator<Map.Entry<Short, Integer>> entries = overrides.entrySet().iterator();
      while(entries.hasNext()) {
        Map.Entry<Short, Integer> entry = entries.next();
        overrideStr += entry.getKey()+":"+entry.getValue()+(entries.hasNext() ? "," : "");
      }
    }
    return overrideStr;
  }
}
